﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Castle.DynamicProxy;
using UnityEngine;

namespace Vasuleg.UIDrive
{
    public class UIDrive : EntityInterceptorImpl
    {
        private readonly List<UIDriveNode> _nodeList = new();
        private readonly Dictionary<string, MemberInfo> _listenerMemberDict = new();
        private readonly HashSet<string> _triggerSet = new();
        
        private HashSet<string> _tempListenerSet;
        private int _listenerCount;
        
        public MonoBehaviour Owner { get; }
        public UIDrive(MonoBehaviour target) : base(target)
        {
            Owner = target;
        }

        public void Init()
        {
            foreach (var driveNode in _nodeList)
            {
                driveNode.Awake();
            }
            foreach (var key in _listenerMemberDict.Keys)
            {
                _triggerSet.Add(key);
            }
            
            _nodeList.Sort((node1, node2) =>
            {
                var isGO1 = node1.Target == node1.TargetGameObject;
                var isGO2 = node2.Target == node2.TargetGameObject;
                if (isGO1 != isGO2)
                {
                    return isGO1 ? 1 : -1;
                }
                if (!isGO1)
                {
                    return 0;
                }
                if (node1.TargetTransform.IsChildOf(node2.TargetTransform))
                {
                    return -1;
                }
                if (node2.TargetTransform.IsChildOf(node1.TargetTransform))
                {
                    return 1;
                }
                
                return -1;


            });
            
            NoticeData();
        }

        public void AddUIDriveNode(UIDriveNode uiDriveNode)
        {
            _nodeList.Add(uiDriveNode);
        }

        public void AddListenerMemberInfo(MemberInfo memberInfo)
        {
            var memberInfoName = memberInfo.Name;
            if (_listenerMemberDict.ContainsKey(memberInfoName))
            {
                return;
            }
            _listenerMemberDict.Add(memberInfoName, memberInfo);
            AddPropSetListener(memberInfoName, OnPropTrigger);
            AddPropGetListener(memberInfoName, OnPropTrigger);
        }


        private void OnPropTrigger(string propName, object _)
        {
            (_tempListenerSet ?? _triggerSet).Add(propName);
        }

        public void BeginListener(HashSet<string> triggerSet = null)
        {
            if (triggerSet != null)
            {
                _tempListenerSet = triggerSet;
                return;
            }
            
            if (_listenerCount++ == 0)
            {
                _triggerSet.Clear();    
            }
        }

        public void EndListener()
        {
            if (_tempListenerSet != null)
            {
                _tempListenerSet = null;
                return;
            }
            if (_listenerCount == 1)
            {
                NoticeData();
            }
            _listenerCount = Math.Max(0, _listenerCount - 1);
        }

        public void NoticeData()
        {
            _listenerCount = 0;
            if (_triggerSet.Count == 0)
            {
                return;
            }
            var context = new NoticeDataContext();
            foreach (var s in _triggerSet)
            {
                if (_listenerMemberDict.TryGetValue(s, out var memberInfo))
                {
                    context.TriggerSet.Add(memberInfo);
                }
            }
            _triggerSet.Clear();
            foreach (var uiDriveNode in _nodeList)
            {
                uiDriveNode.NoticeData(context);
            }
        }

        public override void Intercept(IInvocation invocation)
        {
            var method = invocation.Method;
            if (method.IsSpecialName)
            {
                base.Intercept(invocation);
                return;
            }
            try
            {
                BeginListener();
                invocation.Proceed();
            }
            catch
            {
                EndListener();
                throw;
            }
            EndListener();
        }


        public void Set<T>(string name, T value)
        {
        }

        public T Get<T>(string name)
        {
            return default;
        }
    }
}